package cn.clq.ymcc.service.impl;

import cn.clq.ymcc.constant.CourseOrderStateConstants;
import cn.clq.ymcc.domain.Course;
import cn.clq.ymcc.domain.CourseMarket;
import cn.clq.ymcc.domain.CourseOrder;
import cn.clq.ymcc.domain.CourseOrderItem;
import cn.clq.ymcc.dto.CourseOrderParamsDto;
import cn.clq.ymcc.dto.KillCourseOrderDto;
import cn.clq.ymcc.dto.KillPreOrderParamsDto;
import cn.clq.ymcc.dto.PayDto;
import cn.clq.ymcc.exception.GlobalException;
import cn.clq.ymcc.feign.CoursePermissionFeignClient;
import cn.clq.ymcc.mapper.CourseOrderMapper;
import cn.clq.ymcc.result.JSONResult;
import cn.clq.ymcc.service.ICourseOrderItemService;
import cn.clq.ymcc.service.ICourseOrderService;
import cn.clq.ymcc.util.AssertUtil;
import cn.clq.ymcc.util.CodeGenerateUtils;
import cn.clq.ymcc.vo.CourseItemVo;
import cn.clq.ymcc.vo.CourseOrderVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author stone.chen
 * @since 2022-07-29
 */
@Slf4j
@Service
public class CourseOrderServiceImpl extends ServiceImpl<CourseOrderMapper, CourseOrder> implements ICourseOrderService {

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;
    @Autowired
    private CoursePermissionFeignClient coursePermissionFeignClient;
    @Autowired
    private ICourseOrderItemService courseOrderItemService;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    @Autowired
    private RedissonClient redissonClient;
    /**
     * @description: 课程下单
     * 1.参数校验,JSR303
     * 2.校验token是否有效，需要查询redis
     * 3.业务校验  课程必须存在，课程必须上架，课程Id不能重复,
     * 4.保存订单 （主订单-子订单）
     * 4.1 保存主订单  主订单中的总金额需要由子订单相加
     * 4.2 保存子订单 统计出总金额
     * 4.3 修改主订单金额
     * 5.删除redis中的token
     * @param: courseOrderParamsDto
     * @return: cn.clq.ymcc.result.JSONResult
     * @author clqlongbao
     * @date: 2022/7/29 19:10
     */
    @Override
    @Transactional
    public String placeOrder(CourseOrderParamsDto courseOrderParamsDto) {
        //1.参数校验,JSR303
        //2.校验token是否有效，需要查询redis
        Long loginId = 3L;
        //2.1将集合转换为字符串，方便拼接redis的k
        String courseIds = StringUtils.join(courseOrderParamsDto.getCourseIds(), ",");
        String k = courseIds +":"+loginId;
        System.out.println(k);
        //2.2查询redis中的token
        Object tokenValue = redisTemplate.opsForValue().get(k);
        AssertUtil.isNotNull(tokenValue,"token失效，请重新下单");
        String token = (String)tokenValue;
        System.out.println(token);
        AssertUtil.isEquals(token,courseOrderParamsDto.getToken(),"token错误，请重新下单");
        //3.业务校验  课程必须存在，课程必须上架，课程Id不能重复,需要调用course的feign接口查询数据
        JSONResult jsonResult = coursePermissionFeignClient.searchCourseOrderInfo(courseIds);
        AssertUtil.isTrue(jsonResult.isSuccess(),jsonResult.getMessage());
        AssertUtil.isNotNull(jsonResult.getData(),jsonResult.getMessage());
        //序列化
        String objStr = JSON.toJSONString(jsonResult.getData());
        CourseOrderVo courseOrderVo = JSONObject.parseObject(objStr, CourseOrderVo.class);
        List<CourseItemVo> courseInfos = courseOrderVo.getCourseInfos();
        AssertUtil.isNotNull(courseInfos,jsonResult.getMessage());
        //4.保存订单 （主订单-子订单）
        //4.1 保存主订单  主订单中的总金额需要由子订单相加
        Date date = new Date();
        CourseOrder order = new CourseOrder();
        order.setCreateTime(date);
        order.setOrderNo(CodeGenerateUtils.generateOrderSn(loginId));//工具类生成订单号
        order.setStatusOrder(0);//0：下单成功待支付，1：支付成功订单完成，2：用户手动取消订单(未支付)，3：支付失败，4：超时自动订单取消
        order.setUserId(loginId);
        order.setVersion(0);
        order.setPayType(courseOrderParamsDto.getPayType());
        //insert(order);
        //4.2 保存子订单 统计出总金额，数量，订单标题
        StringBuilder sb = new StringBuilder();
        sb.append("课程【");
        courseInfos.forEach(courseItemVo->{
            Course course = courseItemVo.getCourse();
            CourseMarket courseMarket = courseItemVo.getCourseMarket();
            CourseOrderItem item = new CourseOrderItem();
            //item.setOrderId(order.getId());
            item.setAmount(courseMarket.getPrice());
            item.setCount(1);
            //设置主订单的数量，金额，标题
            order.setTotalCount(order.getTotalCount() + 1);
            order.setTotalAmount(order.getTotalAmount().add(item.getAmount()));
            sb.append(course.getName()).append(",");
            item.setCreateTime(date);
            item.setCourseId(course.getId());
            item.setCourseName(course.getName());
            item.setCoursePic(course.getPic());
            item.setVersion(order.getVersion());
            item.setOrderNo(order.getOrderNo());
            //courseOrderItemService.insert(item);
            order.getCourseOrderItems().add(item);//将每一个子订单都放到他对应的主订单里面去
        });
        sb.append("】订单");
        //4.3 修改主订单
        order.setTitle(sb.toString());
        //updateById(order);

        //5.发送事务消息,将订单的保存操作全都交给MQ的事务监听器来执行
        Map<Object, Object> extparam = new HashMap<>();
        extparam.put("userId",loginId);
        extparam.put("courseId",courseIds);
        PayDto payDto = new PayDto(
                order.getTotalAmount(),
                order.getPayType(),
                null,
                order.getOrderNo(),
                order.getUserId(),
                JSON.toJSONString(extparam),
                order.getTitle()
        );
        log.info("发送事务消息");
        //MQ中经常用来将消息体构建成Message对象
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(payDto)).build();
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(
                "service-order-producer",//和监听器中注解申明的名字一致
                "course-order-topic:course-order-tag",
                message,//消息体，用来生成t_pay_order表的数据库
                order//额外参数，用来传递主订单和子订单信息，交给监听器去保存本地事务
        );
        log.info("发送事务消息完成，检查事务消息发送状态");
        LocalTransactionState localTransactionState = transactionSendResult.getLocalTransactionState();
        SendStatus sendStatus = transactionSendResult.getSendStatus();
        if(localTransactionState != LocalTransactionState.COMMIT_MESSAGE || sendStatus != SendStatus.SEND_OK ){
            log.info("校验发送事务消息状态异常");
            throw new GlobalException("下单失败");
        }
        //6.发布支付超时延迟消息
        try {
            SendResult sendResult = rocketMQTemplate.syncSend(
                    "paytimeout-topic:paytimeout-tag",//topic:tag
                    MessageBuilder.withPayload(order.getOrderNo()).build(),//消息体
                    2000,//发送延迟消息的超时时间
                    4//延迟等级  如果是30分钟 16
            );
            SendStatus sendStatus1 = sendResult.getSendStatus();
            if(sendStatus1 != SendStatus.SEND_OK){
                 /*
                   先尝试重试3次，如果3次重试都发不出去
                     1.记录一个log表
                     2.发站内信，发短信，发邮件通知运维人员，人工接入操作
             */
            }
        } catch (Exception e) {
            e.printStackTrace();
            //报错，我们需要做补偿机制
            /*
             先尝试重试3次，如果3次重试都发不出去
             1.记录一个log表
             2.发站内信，发短信，发邮件通知运维人员，人工接入操作
             */
        }
        //7.删除redis中的token
        redisTemplate.delete(k);
        //8.返回订单号
        return order.getOrderNo();
    }


    /**
     * @description: 保存主订单和子订单
     * @param: courseOrder
     * @return: void
     * @author clqlongbao
     * @date: 2022/7/31 23:41
     */
    @Override
    public void saveOrderAndItems(CourseOrder courseOrder) {
        //先查询一下是否已经下过单了，防止MQ由于重试机制导致的重复下单
        CourseOrder orderTmp = selectOrderByOrderNo(courseOrder.getOrderNo());
        AssertUtil.isNull(orderTmp,"订单已经存在，下单失败");
        //保存主订单
        insert(courseOrder);
        courseOrder.getCourseOrderItems().forEach(item->{
            item.setOrderId(courseOrder.getId());
            //保存子订单
            courseOrderItemService.insert(item);
        });
    }


    /**
     * @description: 根据订单号查询订单
     * @param: orderNo
     * @return: cn.clq.ymcc.domain.CourseOrder
     * @author clqlongbao
     * @date: 2022/7/31 23:46
     */
    @Override
    public CourseOrder selectOrderByOrderNo(String orderNo) {
        Wrapper<CourseOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no",orderNo);
        return selectOne(wrapper);
    }


    /**
     * @description: 支付成功后，回调修改订单状态
     * @param: courseOrder
     * @return: void
     * @author clqlongbao
     * @date: 2022/8/4 1:27
     */
    @Override
    public void payOrder(CourseOrder courseOrder) {
        courseOrder.setStatusOrder(CourseOrderStateConstants.STATE_PAY_SUCCESS);
        courseOrder.setUpdateTime(new Date());
        updateById(courseOrder);
    }

    /**
     * @description: 支付超时，取消订单
     * @param: orderNo
     * @return: void
     * @author clqlongbao
     * @date: 2022/8/4 2:56
     */
    @Override
    public void cancelOrder(String orderNo) {
        //本来应该拿着订单号去支付宝查询一波，看订单是否支付成功，但是这里没有对接支付宝，略过
        CourseOrder courseOrder = selectOrderByOrderNo(orderNo);
        AssertUtil.isNotNull(courseOrder,"订单不存在");
        if(courseOrder.getStatusOrder().intValue() != CourseOrderStateConstants.STATE_PAY_SUCCESS.intValue()){
            courseOrder.setStatusOrder(CourseOrderStateConstants.STATE_CANCEL);
            updateById(courseOrder);
        }
        //支付超时取消的时候，如果是秒杀订单，还需要还库存，并且删除预创订单
        KillPreOrderParamsDto preOrder = (KillPreOrderParamsDto)redisTemplate.opsForValue().get(orderNo);
        if(preOrder != null){
            try {
                Long activityId = preOrder.getActivityId();
                Long killCourseId = preOrder.getKillCourseId();
                String key = activityId+":"+killCourseId;
                RSemaphore semaphore = redissonClient.getSemaphore(key);
                //还信号量
                semaphore.addPermits(preOrder.getKillCount());
                //删除预创订单
                redisTemplate.delete(preOrder.getOrderNo());
            } catch (Exception e) {
                e.printStackTrace();
                //走兜底方案
            }
        }
    }

    /**
     * @description: 秒杀下单,返回订单号
     * 1.参数校验
     * 2.防重复token校验
     * 3.业务校验  从redis中查询预创订单
     * 4.根据预创订单生成主订单和子订单
     * 5.保存订单 （主订单-子订单）
     * 6.发送支付超时消息
     * 7.删除防重复token
     * 8.删除redis中的预创订单
     * @param: dto
     * @return: cn.clq.ymcc.result.JSONResult
     * @author clqlongbao
     * @date: 2022/8/7 1:33
     */
    @Override
    public String killPlaceOrder(KillCourseOrderDto dto) {
        //1.从redis获取预创订单
        KillPreOrderParamsDto preOrder = (KillPreOrderParamsDto) redisTemplate.opsForValue().get(dto.getOrderNo());
        AssertUtil.isNotNull(preOrder,"请求非法！！");
        //2.防重复token校验
        Long loginId = 3L;
        String key = preOrder.getCourseId()+":"+loginId;
        Object tokenTmp = redisTemplate.opsForValue().get(key);
        AssertUtil.isNotNull(tokenTmp,"token失效，请重新下单！");
        AssertUtil.isEquals(dto.getToken(),tokenTmp.toString(),"token错误，请重新下单");
        //4.根据预创订单生成主订单和子订单
        //4.1 生成主订单
        Date date = new Date();
        CourseOrder order = new CourseOrder();
        order.setCreateTime(date);
        order.setOrderNo(preOrder.getOrderNo());
        order.setStatusOrder(0);
        order.setUserId(preOrder.getUserId());
        order.setVersion(0);
        order.setPayType(dto.getPayType());
        order.setTotalAmount(preOrder.getKillAmount());
        order.setTotalCount(preOrder.getKillCount());
        order.setTitle("秒杀下单："+preOrder.getCourseName());
        //insert(order);//保存订单放到本地事务执行
        //4.2 生成子订单 统计出总金额
        CourseOrderItem item = new CourseOrderItem();
        //item.setOrderId(order.getId());
        item.setAmount(preOrder.getKillAmount());
        item.setCount(preOrder.getKillCount());
        item.setCreateTime(date);
        item.setCourseId(preOrder.getCourseId());
        item.setCourseName(preOrder.getCourseName());
        item.setCoursePic(preOrder.getCoursePic());
        item.setVersion(order.getVersion());
        item.setOrderNo(order.getOrderNo());
        //courseOrderItemService.insert(item);//保存订单放到本地事务执行
        order.getCourseOrderItems().add(item);
        //updateById(order);

        //5.发送事务消息,将订单的保存操作全都交给MQ的事务监听器来执行
        /*
          message: 消费者要用到的数据
          arg: 本地事务执行需要的数据
         */
        HashMap<Object, Object> extParam = new HashMap<>();
        extParam.put("userId",loginId);
        extParam.put("courseId",preOrder.getCourseId());
        PayDto payDto = new PayDto(
                order.getTotalAmount(),
                order.getPayType(),
                null,
                order.getOrderNo(),
                order.getUserId(),
                JSON.toJSONString(extParam),
                order.getTitle()
        );
        log.info("发送事务消息");

        //构建消息体
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(payDto)).build();
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(
                "service-order-producer",//要和监听器上注解申明的名字一致
                "course-order-topic:course-order-tag",
                message,
                order
        );
        log.info("发送完事务消息--检查事务消息状态");
        LocalTransactionState localTransactionState = transactionSendResult.getLocalTransactionState();
        SendStatus sendStatus = transactionSendResult.getSendStatus();
        if(localTransactionState != LocalTransactionState.COMMIT_MESSAGE || sendStatus != SendStatus.SEND_OK ){
            log.info("校验发送事务消息状态异常");
            throw new GlobalException("下单失败！！");
        }

        //6.发布支付超时延迟消息
        try {
            SendResult sendResult = rocketMQTemplate.syncSend(
                    "paytimeout-topic:paytimeout-tag",//topic:tag
                    MessageBuilder.withPayload(order.getOrderNo()).build(),//消息体
                    2000,//发送延迟消息的超时时间
                    4//延迟等级  如果是30分钟 16
            );
            SendStatus sendStatus1 = sendResult.getSendStatus();
            if(sendStatus1 != SendStatus.SEND_OK){
              //兜底方案
            }
        } catch (Exception e) {
            e.printStackTrace();
            //兜底方案
        }
        //发布延迟消息结束===================
        //7.删除防重复token
        redisTemplate.delete(key);

        //8.支付超时，或者支付成功删除预创订单
        //redisTemplate.delete(preOrder.getOrderNo());
        return order.getOrderNo();
    }

}
